/*
 *  linux/fs/open.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include "../include/errno.h"
#include "../include/linux/fs.h"
#include "../include/asm/segment.h"
#include "../include/linux/sched.h"
#include "../include/sys/stat.h"
#include "../include/linux/kernel.h"
#include "../include/linux/tty.h"
#include "../include/fcntl.h"
//#include "../include/termios.h"
#include "../include/utime.h"

/* #include <string.h> */


int sys_ustat(int dev, struct ustat *ubuf) {
    return -ENOSYS;
}

int sys_utime(char *filename, struct utimbuf *times) {
    struct m_inode *inode;
    long actime, modtime;

    if (!(inode = namei(filename)))
        return -ENOENT;
    if (times) {
        actime = get_fs_long((unsigned long *) &times->actime);
        modtime = get_fs_long((unsigned long *) &times->modtime);
    } else
        actime = modtime = CURRENT_TIME;
    inode->i_atime = actime;
    inode->i_mtime = modtime;
    inode->i_dirt = 1;
    iput(inode);
    return 0;
}

/*
 * XXX should we use the real or effective uid?  BSD uses the real uid,
 * so as to make this call useful to setuid programs.
 */
int sys_access(const char *filename, int mode) {
    struct m_inode *inode;
    int res, i_mode;

    mode &= 0007;
    if (!(inode = namei(filename)))
        return -EACCES;
    i_mode = res = inode->i_mode & 0777;
    iput(inode);
    if (current->uid == inode->i_uid)
        res >>= 6;
    else if (current->gid == inode->i_gid)
        res >>= 6;
    if ((res & 0007 & mode) == mode)
        return 0;
    /*
     * XXX we are doing this test last because we really should be
     * swapping the effective with the real user id (temporarily),
     * and then calling suser() routine.  If we do call the
     * suser() routine, it needs to be called last.
     */
    if ((!current->uid) &&
        (!(mode & 1) || (i_mode & 0111)))
        return 0;
    return -EACCES;
}

int sys_chdir(const char *filename) {
    struct m_inode *inode;

    if (!(inode = namei(filename)))
        return -ENOENT;
    if (!S_ISDIR(inode->i_mode)) {
        iput(inode);
        return -ENOTDIR;
    }
    iput(current->pwd);
    current->pwd = inode;
    return (0);
}

int sys_chroot(const char *filename) {
    struct m_inode *inode;

    if (!(inode = namei(filename)))
        return -ENOENT;
    if (!S_ISDIR(inode->i_mode)) {
        iput(inode);
        return -ENOTDIR;
    }
    iput(current->root);
    current->root = inode;
    return (0);
}

int sys_chmod(const char *filename, int mode) {
    struct m_inode *inode;

    if (!(inode = namei(filename)))
        return -ENOENT;
    if ((current->euid != inode->i_uid) && !suser()) {
        iput(inode);
        return -EACCES;
    }
    inode->i_mode = (mode & 07777) | (inode->i_mode & ~07777);
    inode->i_dirt = 1;
    iput(inode);
    return 0;
}

int sys_chown(const char *filename, int uid, int gid) {
    struct m_inode *inode;

    if (!(inode = namei(filename)))
        return -ENOENT;
    if (!suser()) {
        iput(inode);
        return -EACCES;
    }
    inode->i_uid = uid;
    inode->i_gid = gid;
    inode->i_dirt = 1;
    iput(inode);
    return 0;
}


static int check_char_dev(struct m_inode * inode, int dev, int flag)
{
    struct tty_struct *tty;
    int min;

    if (MAJOR(dev) == 4 || MAJOR(dev) == 5) {
        if (MAJOR(dev) == 5)
            min = current->tty;
        else
            min = MINOR(dev);
        if (min < 0)
            return -1;
        if ((IS_A_PTY_MASTER(min)) && (inode->i_count>1))
            return -1;
        tty = TTY_TABLE(min);
        if (!(flag & O_NOCTTY) &&
            current->leader &&
            current->tty<0 &&
            tty->session==0) {
            current->tty = min;
            tty->session= current->session;
            tty->pgrp = current->pgrp;
        }
        if (flag & O_NONBLOCK) {
            TTY_TABLE(min)->termios.c_cc[VMIN] =0;
            TTY_TABLE(min)->termios.c_cc[VTIME] =0;
            TTY_TABLE(min)->termios.c_lflag &= ~ICANON;
        }
    }
    return 0;
}



////打开（或创建）文件系统调用。
//参数 filename 是文件名，flag 是打开文件标志，它可取值：ORDONLY（只读）、O_ WRONLY（只写）或ORDWR（读写），
// 以及O_CREAT（创建）、0_EXCL（被创建文件必须不存在）、0_APPEND（在文件尾添加数据）等其他一些标志的组合。
// 如果创建了一个新文件，则mode 就用于指定文件的许可属性。这些属性有S_IRWXU（文件宿主具有读、写和执行权限）、
// SIRUSR(用户具有读文件权限）、SIRWXG（组成员具有读、写和执行权限）等等。对于新创建的文件，
// 这些属性只应用于将来对文件的访问，创建了只读文件的打开调用也将返回一个可读写的文件句柄。
// 如果调用操作成功，则返回文件句柄(文件描述符)，否则返回出错码。
//参见 sys/stat. h、 fentl.h。
int sys_open(const char *filename, int flag, int mode) {
    struct m_inode *inode;
    struct file *f;
    int i, fd;

    //将用户设置的文件模式和进程模式屏蔽码相与，产生许可的文件模式。
    mode &= 0777 & ~current->umask;
    // 遍历进程结构中的文件结构指针数组，以查找一个空闲项，一个进程同时最大打开文件数是 NR_OPEN。
    // 索引号fd 即是本次打开文件的句柄值。
    for (fd = 0; fd < NR_OPEN; fd++)
        if (!current->filp[fd])
            break;
    //在NR_OPEN大小的filp数组中，没有找到空闲项，则返回出错码（参数无效）。
    if (fd >= NR_OPEN)
        return -EINVAL;
    //设置当前进程的执行时关闭文件句柄(close_on_exec)位图，复位对应的比特位。
    // close_on_exec 是一个进程所有文件句柄的位图标志。每个比特位代表一个打开着的文件描述符,
    // 用于确定在调用系统调用 execve ()时需要关闭的文件句柄。
    // 当程序使用 fork() 函数创建了一个子进程时，通常会在该子进程中调用 execve(） 函数加载执行另一个新程序。
    // 此时 子进程中开始执行新程序。若一个文件句柄在 close_on_exec 中的对应比特位被置位，那么在执行 execve()时关闭对应文件句柄，否则该文件句柄将始终处于打开状态。
    // 当打开一个文件时，默认情况下文件句柄在子进程中也处于打开状态。因此这里要复位对应比特位。
    current->close_on_exec &= ~(1 << fd);

    // 文件表是内核中的一个全局数组，它的每个元素都是一个文件结构。系统最多打开 NR_FILE 个文件。
    // 令f指向文件表数组开始处。“0+file_table〞等同于〞file_table"和”&file_table[O]〞
    f = 0 + file_table;
    // 内核中的 文件表中寻找一个空闲结构项。
    for (i = 0; i < NR_FILE; i++, f++)
        if (!f->f_count) break;
    // 若已经没有空闲文件表结构项，则返回出错码。
    if (i >= NR_FILE)
        return -EINVAL;
    // 进程对应文件句柄fd 的文件结构指针 = 文件结构，并令文件引用计数递增1。
    (current->filp[fd] = f)->f_count++;
    //open_ namei()执行打开操作，
    if ((i = open_namei(filename, flag, mode, &inode)) < 0) {
        // 若返回值小于 0，则说明出错，于是释放刚申请到的文件结构，返回出错码 i。
        current->filp[fd] = NULL;
        f->f_count = 0;
        return i;
    }
    // 若文件打开操作成功，则 inode 是己打开文件的i节点指针。

    // 根据已打开文件的i节点的属性字段，得到文件的具体类型。
    // 打开不同类型的文件，处理不同。
    // 1、打开字符设备文件，
    //  - 主设备号是4的字符文件(例如/dev/tty0)，如果当前进程是进程组首领并且当前进程的 tty 字段小于0（没有终端），
    // 则设置当前进程的tty 号为该i节点的子设备号，并设置当前进程 tty 对应的tty 表项的父进程组号等于当前进程的进程组号。表示为该进程组（会话期）分配控制终端。
    //  - 主设备号是 5的字符文件（/dew/tty），若当前进程没有 tty，则说明出错，于是放回i节点和申请到的文件结构，返回出错码（无许可）
    // （注：这段代码存在问题）

/* ttys are somewhat special (ttyxx major==4, tty major==5) */
    if (S_ISCHR(inode->i_mode)) {
        if (check_char_dev(inode,inode->i_zone[0],flag)) {
            iput(inode);
            current->filp[fd]=NULL;
            f->f_count=0;
            return -EAGAIN;
        }
    }
    //2、 打开块设备文件，则检查盘片是否更换过。若更换过则需要让高速缓冲区中该设备的所有缓冲块失效。
/* Likewise with block-devices: check for floppy_change */
    if (S_ISBLK(inode->i_mode))
        check_disk_change(inode->i_zone[0]);
    // 设置文件结构属性和标志，
    f->f_mode = inode->i_mode;
    f->f_flags = flag;
    // 置句柄引用计数为1
    f->f_count = 1;
    //设置i节点字段为打开文件的i节点，
    f->f_inode = inode;
    // 初始化文件读写指针为0。
    f->f_pos = 0;
    // 最后返回文件句柄号。
    return (fd);
}

int sys_creat(const char *pathname, int mode) {
    return sys_open(pathname, O_CREAT | O_TRUNC, mode);
}

int sys_close(unsigned int fd) {
    struct file *filp;

    if (fd >= NR_OPEN)
        return -EINVAL;
    current->close_on_exec &= ~(1 << fd);
    if (!(filp = current->filp[fd]))
        return -EINVAL;
    current->filp[fd] = NULL;
    if (filp->f_count == 0)
        panic("Close: file count is 0");
    if (--filp->f_count)
        return (0);
    iput(filp->f_inode);
    return (0);
}